// ***********************************************************
//
//   CCodeCounter.cpp - main class handling common features of CodeCount
//
//   REVISION  DATE     AUTHOR				MODIFICATION
//   Rev-1    Fall 07   WHH					Original author, CSSE
//   Rev-2    9/13/08   VN (Vu Nguyen)      Updated ReplaceQuote function
// ***********************************************************

#include "CCodeCounter.h"
#include <time.h>

CCodeCounter::CCodeCounter()
{
	isPrintKeyword = false;
	QuoteStart = "";
	QuoteEnd = "";
	QuoteEscapeFront = 0;
	QuoteEscapeRear = 0;
	ContinueLine = 0;
	classtype = DATAFILE;
	language_name = "Datafile";
	casesensitive = true;
	total_filesA = 0;
	total_filesB = 0;
}

CCodeCounter::~CCodeCounter()
{
}

// Initialize the count vectors. The method removes the existing content of the vectors
//	and assigns them all zeros
void CCodeCounter::InitializeCounts()
{
	directive_count.assign(directive.size(), make_pair(0,0));
	data_name_count.assign(data_name_list.size(), make_pair(0,0));
	exec_name_count.assign(exec_name_list.size(), make_pair(0,0));
	math_func_count.assign(math_func_list.size(), make_pair(0,0));
	trig_func_count.assign(trig_func_list.size(), make_pair(0,0));
	log_func_count.assign(log_func_list.size(), make_pair(0,0));

	cmplx_calc_count.assign(cmplx_calc_list.size(), make_pair(0,0));
	cmplx_cond_count.assign(cmplx_cond_list.size(), make_pair(0,0));
	cmplx_logic_count.assign(cmplx_logic_list.size(), make_pair(0,0));
	cmplx_preproc_count.assign(cmplx_preproc_list.size(), make_pair(0,0));
	cmplx_assign_count.assign(cmplx_assign_list.size(), make_pair(0,0));
	cmplx_pointer_count.assign(cmplx_pointer_list.size(), make_pair(0,0));
}

// Method: CountSLOC(...)
// Description: Process and count the source file
// Parameters:
// -- contd: specifies the quote string is continued from the previous line
// -- strline: the string to be processed
// -- CurrentQuoteEnd: the end quote of the current status
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     Spr 07   CPA     Original author
// Rev-2     Fall 07  WHH     Updated
// Rev-3     11/16/09 VN      Replaced ReplaceQuote(), CountCommentsSLOC by 
//                             QuoteCountCommentsSLOC()
//                             fmapBak is same as fmap except that it stores
//                             unmodified quoted strings. fmap has quoted strings 
//                             replaced with '$'
int CCodeCounter::CountSLOC(filemap* fmap, results* result)
{
	filemap fmapBak = *fmap; // backup the file content before modifying it
//	ReplaceQuote(fmap);

	CountBlankSLOC(fmap, result);

//	CountCommentsSLOC(fmap,result);
	CountCommentsSLOC(fmap, result, &fmapBak);

	CountComplexity(fmap, result); //AMB: call to complexity function

	CountDirectiveSLOC(fmap, result, &fmapBak);
	/*//testing
	for(vector<lineElement>::iterator j = fmap->begin(); j!=fmap->end(); j++)
	{
		cout<<j->line<<endl;
	}
	cout<<endl<<"------------"<<endl<<"directive phy:"<<result->directive_lines[PHY]<<" | directive log:"<<result->directive_lines[LOG]<<endl;
	cin.get();
	*/

	LanguageSpecificProcess(fmap, result, &fmapBak);

	return 0;
}

bool CCodeCounter::isSupportedFileExtension(string file_name)
{
	size_t idx = file_name.find_last_of(".");
	if (idx == string::npos) return false;
	string file_ext = file_name.substr(idx);
	file_ext = CUtil::to_lower(file_ext);
	if (find(file_extension.begin(),file_extension.end(),file_ext) != file_extension.end())
		return true;
	else
		return false;
}

ofstream* CCodeCounter::getOutputStream(string outputFileNamePrePend)
{
	if (!output_file.is_open())
	{
		string fname = outputFileNamePrePend + language_name + output_file_name;
		output_file.open(fname.c_str(),ios::out);

		if (!output_file.is_open()) return NULL;

		CUtil::PrintFileHeader(output_file, "SLOC COUNT RESULTS");

		output_file<<string(34,' ')<<"RESULTS FOR "<<language_name<<" FILES"<<endl<<endl;
		output_file<<" Total  Blank |    Comments    | Compiler  Data   Exec.  |Logical | File  Module"<<endl;
		output_file<<" Lines  Lines | Whole Embedded | Direct.   Decl.  Instr. |  SLOC  | Type   Name"<<endl;
		output_file<<string(110,'-')<<endl;
	}
	return &output_file;
}

void CCodeCounter::closeOutputStream()
{
	if (output_file.is_open())
		output_file.close();
}

// Find the first index of one of the characters of strQuote in strline
size_t CCodeCounter::FindQuote(string const &strline, string const &strQuote, size_t idx_start, char QuoteEscapeFront)
{
	size_t min_idx, idx;
	min_idx = strline.length();
	for (size_t i = 0; i < strQuote.length(); i++)
	{
		idx = CUtil::FindCharAvoidEscape(strline, strQuote[i], idx_start, QuoteEscapeFront);
		if (idx != string::npos && idx < min_idx)
			min_idx = idx;
	}

	if (min_idx < strline.length()) return min_idx;
	return string::npos;
}

// Method: ReplaceQuote(...)
// Description: Replace up to ONE quoted string inside a string
//              starting at idx_start
// All parameters are modified in the method:
// -- contd: specifies the quote string is continued from the previous line
// -- strline: the string to be processed
// -- idx_start: the location to start look for quoted string
// -- CurrentQuoteEnd: the end quote of the current status
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     11/16/08   VN      Original author, added based on ReplaceQuote() function

int CCodeCounter::ReplaceQuote(string &strline, size_t &idx_start, bool &contd, char &CurrentQuoteEnd)
{
	size_t idx_end, idx_quote;

	if (contd)
	{
		idx_start = 0;
		strline[0] = '$';
	}
	else
	{
		// VN: handle two quote chars in JavaScript, both " and ' are accepted
		//idx_start = CUtil::FindCharAvoidEscape(strline, QuoteStart[0], idx_start, QuoteEscapeFront);
		idx_start = FindQuote(strline, QuoteStart, idx_start, QuoteEscapeFront);
		if (idx_start != string::npos)
		{
			idx_quote = QuoteStart.find_first_of(strline[idx_start]);
			CurrentQuoteEnd = QuoteEnd[idx_quote];
		}
		else
		{
			return 0;
		}
	}

	idx_end = CUtil::FindCharAvoidEscape(strline, CurrentQuoteEnd, idx_start + 1, QuoteEscapeFront);
	if (idx_end==string::npos)
	{
		idx_end = strline.length()-1;

		strline.replace(idx_start+1,idx_end-idx_start,idx_end-idx_start,'$');
		contd = true;
		idx_start = idx_end;
	}
	else
	{
		if ((QuoteEscapeRear) && (strline.length()>idx_end+1) && (strline[idx_end+1] == QuoteEscapeRear))
		{
			strline[idx_end] = '$';
			strline[idx_end+1] = '$';
		}
		else
		{
			contd = false;
			strline.replace(idx_start+1,idx_end-idx_start-1,idx_end-idx_start-1,'$');
			idx_start = strline.find_first_of(QuoteStart,idx_end+1);
		}

		idx_start = idx_end + 1;
	}

	return 1;
}

int CCodeCounter::CountBlankSLOC(filemap* fmap, results* result)
{
	filemap::iterator i;
	for (i = fmap->begin(); i != fmap->end(); i++)
	{
		if (CUtil::CheckBlank(i->line))
		{	// checking for a blank line	
			result->blank_lines++;		// iterating the file info of blank lines
		}
	}

	return 1;
}

// Method: FindCommentStart(...)
// Description: Find a starting position of a comment in a string
//              starting at idx_start
// Revision  Date     Author  Modification
// Rev-1     11/16/08 VN      Original author
int CCodeCounter::FindCommentStart(string strline, size_t &idx_start, int &comment_type,
								   string &curBlckCmtStart, string &curBlckCmtEnd) 
{
	size_t idx_line, idx_tmp, idx_block;
	string line = strline;
	comment_type = 0;

	if (!casesensitive) line = CUtil::to_lower(line);

	//searching for starting of line comment
	idx_line = string::npos;
	for (StringVector::iterator i = LineCommentStart.begin(); i != LineCommentStart.end(); i++)
	{
		idx_tmp = line.find((casesensitive ? (*i) : CUtil::to_lower(*i)), idx_start);
		if (idx_tmp < idx_line) idx_line = idx_tmp;
	}

	//searching for starting of block comment
	idx_block = string::npos;
	for (StringVector::iterator i = BlockCommentStart.begin(); i != BlockCommentStart.end(); i++)
	{
		idx_tmp = strline.find(*i, idx_start);
		if (idx_tmp < idx_block)
		{
			idx_block = idx_tmp;
			curBlckCmtStart = *i;
			curBlckCmtEnd = *(BlockCommentEnd.begin()+(i-BlockCommentStart.begin()));
		}
	}

	//see what kind of comment appear first
	if (idx_line == string::npos && idx_block == string::npos)
	{
		comment_type = 0;
		idx_start = idx_line;
	}
	else if (idx_line > idx_block)
	{
		idx_start = idx_block;
		comment_type = idx_start == 0 ? 3 : 4;
	}
	else
	{
		idx_start = idx_line;
		comment_type = idx_start == 0 ? 1 : 2;
	}
	return 1;
}

// Method: CountCommentsSLOC(...)
// Description: Replace quoted strings by special chars, e.g., $
//              Count the number of comment lines and remove comments.
//              All arguments are modified by the method
// Revision  Date     Author  Modification
// Rev-1     11/16/08 VN      Original author, based on the CountCommentsSLOC method
//                            Handled the issue of the original version on the following case
//                              printf("string \ // this is a comment \ abc
//                            Handled multiple-line comments
int CCodeCounter::CountCommentsSLOC(filemap* fmap, results* result, filemap *fmapBak)
{
	if (BlockCommentStart.empty() && LineCommentStart.empty()) return 0;
	if (classtype == DATAFILE) return 0;

	bool contd = false;
	bool contd_nextline;
	int comment_type = 0;
	/*
	comment_type:
	0 : not comment
	1 : line commnet, whole line
	2 : line comment, embedded
	3 : block comment, undecided
	4 : block comment, embedded
	*/

	size_t idx_start, idx_end;
	size_t quote_idx_start;
	string curBlckCmtStart, curBlckCmtEnd;
	char CurrentQuoteEnd = 0;
	bool quote_contd = false;
	filemap::iterator itfmBak = fmapBak->begin();

	quote_idx_start = 0;

	for(filemap::iterator iter = fmap->begin(); iter != fmap->end(); iter++, itfmBak++)
	{
		if (CUtil::CheckBlank(iter->line)) continue;
		contd_nextline = false;

		quote_idx_start = 0;
		idx_start = 0;

		if (quote_contd)
		{
			// Replace quote until next character
			ReplaceQuote(iter->line, quote_idx_start, quote_contd, CurrentQuoteEnd);
			if (quote_contd) continue;
		}

		if (contd)
		{
			comment_type = 3;
		}

		while (!contd_nextline && idx_start < iter->line.length())
		{
			// VN: need to handle two quote chars in JavaScript, both " and ' are accepted
			//quote_idx_start = CUtil::FindCharAvoidEscape(iter->line, QuoteStart[0], quote_idx_start, QuoteEscapeFront);
			quote_idx_start = FindQuote(iter->line, QuoteStart, quote_idx_start, QuoteEscapeFront);
			if (!contd) FindCommentStart(iter->line, idx_start, comment_type, curBlckCmtStart, curBlckCmtEnd);

			// if found quote before comment, e.g., "this is quote");//comment
			if (quote_idx_start != string::npos && quote_idx_start < idx_start)
			{
				ReplaceQuote(iter->line, quote_idx_start, quote_contd, CurrentQuoteEnd);
				if (quote_idx_start > idx_start)
				{
					idx_start = quote_idx_start;
					continue; // comment delimiter inside quote
				}
			}
			else if (idx_start != string::npos) // comment delimiter starts first
			{
				switch(comment_type)
				{
				case 1:	//line comment, whole line
					result->comment_lines++;
					iter->line = "";
					itfmBak->line = "";
					contd_nextline = true;
					break;
				case 2:	//line comment, embedded
					result->e_comm_lines++;
					iter->line = iter->line.substr(0,idx_start);
					itfmBak->line = itfmBak->line.substr(0,idx_start);
					contd_nextline = true;
					break;
				case 3:	//block comment
				case 4:
					if (contd)
					{
						idx_end = iter->line.find(curBlckCmtEnd);
					}
					else
					{
						idx_end = iter->line.find(curBlckCmtEnd,idx_start+curBlckCmtStart.length());
					}

					if (idx_end==string::npos)
					{
						if (comment_type == 3)
						{
							iter->line = "";
							itfmBak->line = "";
							result->comment_lines++;
						}
						else if (comment_type == 4)
						{
							iter->line = iter->line.substr(0,idx_start);
							itfmBak->line = itfmBak->line.substr(0,idx_start);
							result->e_comm_lines++;
						}
						contd = true;
						contd_nextline = true;
						break;
					}
					else
					{
						contd = false;
						iter->line.erase(idx_start, idx_end - idx_start + curBlckCmtEnd.length());
						itfmBak->line.erase(idx_start, idx_end - idx_start + curBlckCmtEnd.length());
						if (iter->line.empty())
						{
							result->comment_lines++;
						}
						else
						{
							result->e_comm_lines++;
						}

						// quote chars found may be erased as it is inside comment
						quote_idx_start = idx_start;
					}

					break;
				default:
					cout<<"Error in CountCommentsSLOC()";
					break;
				}
			}
		}	//while
	}	//for

	return 1;
}

int CCodeCounter::LanguageSpecificProcess(filemap* fmap, results* result, filemap *fmapBak)
{
	for(filemap::iterator iter = fmap->begin(); iter != fmap->end(); iter++)
	{
		if (!CUtil::CheckBlank(iter->line)) result->exec_lines[PHY]++;
	}
	return 1;
}

// AMB: Function to Count Complexity
int CCodeCounter::CountComplexity(filemap* fmap, results* result)
{
//	bool blank_flag	= false;
	filemap::iterator fit;
	int cnt;
	string line;
	string exclude = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$";

	for(fit = fmap->begin(); fit != fmap->end(); fit++)
	{
		line = fit->line;

		if (CUtil::CheckBlank(line))
		{
			continue;
		}

		line = " " + line;

		/*****************************************************************************/
		/*   AMB                                                                     */
		/* Logical Operator processing (for complexity Analysis)					 */
		/* - Use CMPLX_LOGIC_LIST to determine that a Logical Operator has been used */
		/* - declare sloc to be a Logic func line accordingly						 */
		/*																			 */
		/*	 Use : cmplx_logic_lines												 */
		/*****************************************************************************/
		// AMB
		cnt=0;
		CUtil::CountTally(line, cmplx_logic_list, cnt, 1, exclude, "", "", &cmplx_logic_count, result->duplicate, casesensitive);
		result->cmplx_logic_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Math function processing (for complexity Analysis)						 */
		/*   - Use MATH_FUNC_LIST to determine that a Math Func has been detected    */
		/*   - declare sloc to be a Math func line accordingly						 */
		/*																			 */
		/*	 Use : cmplx_math_lines													 */
		/*****************************************************************************/
		// MMG
		cnt=0;
		CUtil::CountTally(line, math_func_list, cnt, 1, exclude, "", "", &math_func_count, result->duplicate, casesensitive); // MMG
		result->cmplx_math_lines += cnt;

		/*****************************************************************************/
		/*   RST                                                                     */
		/* Pointer Processing (for complexity Analysis)								 */
		/* - Use CMPLX_POINTER_LIST to determine POINTER							 */
		/*																			 */
		/*	 Use : cmplx_pointer_lines												 */
		/*****************************************************************************/
		// RST
		cnt=0;
		CUtil::CountTally(line, cmplx_pointer_list, cnt, 1, exclude, "", "", &cmplx_pointer_count, result->duplicate, casesensitive);
		result->cmplx_pointer_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Math function processing (for complexity Analysis)						 */
		/*   - Use TRIG_FUNC_LIST to determine that a Math Func has been detected    */
		/*   - declare sloc to be a trig func line accordingly						 */
		/*																			 */
		/*	 Use : cmplx_trig_lines													 */
		/*****************************************************************************/
		cnt=0;
		CUtil::CountTally(line, trig_func_list, cnt, 1, exclude, "", "", &trig_func_count, result->duplicate, casesensitive);
		result->cmplx_trig_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Math function processing (for complexity Analysis)						 */
		/*   - Use LOG_FUNC_LIST to determine that a Math Func has been detected     */
		/*   - declare sloc to be a logrithm func line accordingly					 */
		/*																			 */
		/*	 Use : cmplx_logrithm_lines 											 */
		/*****************************************************************************/
		cnt=0;
		CUtil::CountTally(line, log_func_list, cnt, 1, exclude, "", "", &log_func_count, result->duplicate, casesensitive);
		result->cmplx_logrithm_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Calculation processing (for complexity Analysis)							 */
		/*   - Use CMPLX_CALC_LIST to determine that a calculation has been detected */
		/*   - declare sloc to be a CALC line accordingly							 */
		/*																			 */
		/*	 Use : cmplx_calc_lines		 											 */
		/*****************************************************************************/
		cnt=0;
		CUtil::CountTally(line, cmplx_calc_list, cnt, 1, exclude, "", "", &cmplx_calc_count, result->duplicate, casesensitive);
		result->cmplx_calc_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Calculation processing (for complexity Analysis)							 */
		/*   - Use CMPLX_COND_LIST to determine that a conditional has been detected */
		/*   - declare sloc to be a CALC line accordingly							 */
		/*																			 */
		/*	 Use : cmplx_cond_lines		 											 */
		/*****************************************************************************/
		cnt=0;
		CUtil::CountTally(line, cmplx_cond_list, cnt, 1, exclude, "", "", &cmplx_cond_count, result->duplicate, casesensitive);
		result->cmplx_cond_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Preprocessor Directive processing (for complexity Analysis)				 */
		/*   - Use preproc_list to determine that a preproc has been detected        */
		/*   - declare sloc to be a preproc directive line accordingly  			 */
		/*																			 */
		/*	 Use : 	cmplx_prepoc_lines												 */
		/*****************************************************************************/
		// AMB: modified
		cnt = 0;
		CUtil::CountTally(line, cmplx_preproc_list, cnt, 1, exclude, "", "", &cmplx_preproc_count, result->duplicate, casesensitive); // MMG
		result->cmplx_prepoc_lines += cnt;

		/*****************************************************************************/
		/*   MMG                                                                     */
		/* Assignment processing (for complexity Analysis)							 */
		/*   - Use CMPLX_ASSIGN_LIST to determine that a Math Func has been detected */
		/*   - declare sloc to be a CALC line accordingly							 */
		/*																			 */
		/*	 Use : cmplx_assign_lines		 										 */
		/*****************************************************************************/
		cnt=0;
		CUtil::CountTally(line, cmplx_assign_list, cnt, 1, exclude, "", "", &cmplx_assign_count, result->duplicate, casesensitive);
		result->cmplx_assign_lines += cnt;
	}
	return 1;
}
